/*
 * @(#)Query.java
 *
 * @author      Orlin Tomov
 * 
 */
package dbu.sql;

import java.util.Collection;
import java.util.Iterator;
import java.util.Stack;
import java.util.StringTokenizer;
import java.util.Vector;

public class Query {

	private Criteria criteria;

	public Query(String infix) {
		if (infix != null) {
			criteria = parse(infix);
		}
	}

	public Criteria getCriteria() {
		return criteria;
	}

	private Collection<String> splitInfix(String infix) {
		Collection<String> tokens = new Vector<String>();
		String subTest = "";
		int commas = 0;
		StringTokenizer st = new StringTokenizer(infix, "' ()", true);
		while (st.hasMoreTokens()) {
			String token = st.nextToken();
			if (token.equals("'")) {
				subTest += token;
				commas++;
			} else if ((token.equalsIgnoreCase(Operator.AND.toString()) || token.equalsIgnoreCase(Operator.OR.toString()) || token.equals("(") || token.equals(")")) && ((commas % 2) == 0)) {
				subTest = subTest.trim();
				if (!subTest.isEmpty()) {
					tokens.add(subTest);
				}
				subTest = "";
				commas = 0;
				tokens.add(token);
			} else {
				subTest += token;
			}
		}
		if (!subTest.isEmpty()) {
			tokens.add(subTest);
		}
		return tokens;
	}

	/** convert infix notation into reverse Polish notation */
	private Collection<String> infixToRPN(String infix) {
		Vector<String> rpn = new Vector<String>();
		Stack<String> stack = new Stack<String>();
		Iterator<String> tokens = splitInfix(infix).iterator();

		/** For each tokens */
		while (tokens.hasNext()) {
			String token = tokens.next();
			/** If token is an operator */
			if (token.equalsIgnoreCase(Operator.AND.toString()) || token.equalsIgnoreCase(Operator.OR.toString())) {
				/** Push the new operator on the stack */
				stack.push(token);
			}
			/** If token is a left bracket '(' */
			else if (token.equals("(")) {
				stack.push(token); //
			}
			/** If token is a right bracket ')' */
			else if (token.equals(")")) {
				while (!stack.empty() && !stack.peek().equals("(")) {
					rpn.add(stack.pop());
				}
				stack.pop();
			}
			/** If token is "a number" */
			else if (!token.isEmpty()) {
				rpn.add(token);
			}
		}
		while (!stack.empty()) {
			rpn.add(stack.pop());
		}
		return rpn;
	}

	/** parse infix */
	private Criteria parse(String infix) {
		Iterator<String> rpn = infixToRPN(infix).iterator();
		Criteria r = null;
		Stack<Object> stack = new Stack<Object>();
		while (rpn.hasNext()) {
			String token = rpn.next();
			if (token.equalsIgnoreCase(Operator.AND.toString()) || token.equalsIgnoreCase(Operator.OR.toString())) {
				Criteria a = null;
				Criteria b = null;
				if (!stack.empty()) {
					if (stack.peek() instanceof Criteria) {
						a = (Criteria) stack.pop();
					} else {
						a = Criteria.getCriteria((String)stack.pop());
					}
				}
				if (stack.empty()) {
					r = a;
				} else {
					if (stack.peek() instanceof Criteria) {
						b = (Criteria) stack.pop();
					} else {
						b = Criteria.getCriteria((String)stack.pop());
					}
				}
				if (token.equalsIgnoreCase(Operator.AND.toString())) {
					r = new Criteria(a, Operator.AND, b);
				}
				if (token.equalsIgnoreCase(Operator.OR.toString())) {
					r = new Criteria(a, Operator.OR, b);
				}
				stack.push(r);
			} else {
				stack.push(token);
			}
		}
		if ((r == null) && !stack.empty()) {
			r = Criteria.getCriteria((String)stack.pop());
		}
		return r;
	}
}
